<?php
// +-------------------------------------------------------------------
// | 
// +-------------------------------------------------------------------
// | Copyright (c) 2009-2016 All rights reserved.
// +-------------------------------------------------------------------
namespace Kcdns\Service\Util\SecureEntry;

class Post
{
    // 通过 HTTP 请求头传输 POST TOKEN
    CONST POST_TOKEN_PARAM = 'HTTP_TOKEN';
    
    // 令牌时间参数名
    CONST POST_TOKEN_EXPIRE_KEY = 'TOKEN_T';
    
    // SESSION 数据密钥 key
    CONST SESSION_POST_KEY = 'SECURE_ENTRY_POST_KEY';

    protected static $config = [];

    protected static $route = '';

    public static function init ($config, $route)
    {
        self::$config = $config;
        self::$route = $route;
    }

    /**
     * *******************************************************************************************
     * *************************************** 校验 post 令牌 *************************************
     * *******************************************************************************************
     */
    public static function check ($force = false)
    {
        if (IS_POST && (self::_isProtected(self::$route) || $force))
        {
            $token = isset($_SERVER[self::POST_TOKEN_PARAM]) ? $_SERVER[self::POST_TOKEN_PARAM] : (isset($_POST['TOKEN']) ? $_POST['TOKEN'] : '');
            
            // 检查令牌生成时间
            if (! isset($_POST[self::POST_TOKEN_EXPIRE_KEY]) || $_POST[self::POST_TOKEN_EXPIRE_KEY] > time() || time() - $_POST[self::POST_TOKEN_EXPIRE_KEY] > self::$config['SECURE_ENTRY_POST_EXPIRE'])
            {
                throw new \Exception('INVALID REQUEST [PT]');
            }
            
            if (! $token || $token !== self::_getToken())
            {
                throw new \Exception('INVALID REQUEST [PS]');
            }
        }
        return true;
    }

    /**
     * *******************************************************************************************
     * *************************************** 生成 post 令牌 *************************************
     * *******************************************************************************************
     */
    public static function token ()
    {
        $_SESSION[self::SESSION_POST_KEY] = $_SESSION[self::SESSION_POST_KEY] ?  : uniqid();
        $key = $_SESSION[self::SESSION_POST_KEY];
        $time = time();
        // 使用 $.ajaxPrefilter + md5 对提交数据进行动态签名
        return <<<EOF
        <script>(function(){(function(f){function p(u,z){var w=(u&65535)+(z&65535);var v=(u>>16)+(z>>16)+(w>>16);return(v<<16)|(w&65535)}function o(u,v){return(u<<v)|(u>>>(32-v))}function j(A,w,v,u,z,y){return p(o(p(p(w,A),p(u,y)),z),v)}function a(w,v,B,A,u,z,y){return j((v&B)|((~v)&A),w,v,u,z,y)}function g(w,v,B,A,u,z,y){return j((v&A)|(B&(~A)),w,v,u,z,y)}function m(w,v,B,A,u,z,y){return j(v^B^A,w,v,u,z,y)}function s(w,v,B,A,u,z,y){return j(B^(v|(~A)),w,v,u,z,y)}function c(F,A){F[A>>5]|=128<<(A%32);F[(((A+64)>>>9)<<4)+14]=A;var w;var z;var y;var v;var u;var E=1732584193;var D=-271733879;var C=-1732584194;var B=271733878;for(w=0;w<F.length;w+=16){z=E;y=D;v=C;u=B;E=a(E,D,C,B,F[w],7,-680876936);B=a(B,E,D,C,F[w+1],12,-389564586);C=a(C,B,E,D,F[w+2],17,606105819);D=a(D,C,B,E,F[w+3],22,-1044525330);E=a(E,D,C,B,F[w+4],7,-176418897);B=a(B,E,D,C,F[w+5],12,1200080426);C=a(C,B,E,D,F[w+6],17,-1473231341);D=a(D,C,B,E,F[w+7],22,-45705983);E=a(E,D,C,B,F[w+8],7,1770035416);B=a(B,E,D,C,F[w+9],12,-1958414417);C=a(C,B,E,D,F[w+10],17,-42063);D=a(D,C,B,E,F[w+11],22,-1990404162);E=a(E,D,C,B,F[w+12],7,1804603682);B=a(B,E,D,C,F[w+13],12,-40341101);C=a(C,B,E,D,F[w+14],17,-1502002290);D=a(D,C,B,E,F[w+15],22,1236535329);E=g(E,D,C,B,F[w+1],5,-165796510);B=g(B,E,D,C,F[w+6],9,-1069501632);C=g(C,B,E,D,F[w+11],14,643717713);D=g(D,C,B,E,F[w],20,-373897302);E=g(E,D,C,B,F[w+5],5,-701558691);B=g(B,E,D,C,F[w+10],9,38016083);C=g(C,B,E,D,F[w+15],14,-660478335);D=g(D,C,B,E,F[w+4],20,-405537848);E=g(E,D,C,B,F[w+9],5,568446438);B=g(B,E,D,C,F[w+14],9,-1019803690);C=g(C,B,E,D,F[w+3],14,-187363961);D=g(D,C,B,E,F[w+8],20,1163531501);E=g(E,D,C,B,F[w+13],5,-1444681467);B=g(B,E,D,C,F[w+2],9,-51403784);C=g(C,B,E,D,F[w+7],14,1735328473);D=g(D,C,B,E,F[w+12],20,-1926607734);E=m(E,D,C,B,F[w+5],4,-378558);B=m(B,E,D,C,F[w+8],11,-2022574463);C=m(C,B,E,D,F[w+11],16,1839030562);D=m(D,C,B,E,F[w+14],23,-35309556);E=m(E,D,C,B,F[w+1],4,-1530992060);B=m(B,E,D,C,F[w+4],11,1272893353);C=m(C,B,E,D,F[w+7],16,-155497632);D=m(D,C,B,E,F[w+10],23,-1094730640);E=m(E,D,C,B,F[w+13],4,681279174);B=m(B,E,D,C,F[w],11,-358537222);C=m(C,B,E,D,F[w+3],16,-722521979);D=m(D,C,B,E,F[w+6],23,76029189);E=m(E,D,C,B,F[w+9],4,-640364487);B=m(B,E,D,C,F[w+12],11,-421815835);C=m(C,B,E,D,F[w+15],16,530742520);D=m(D,C,B,E,F[w+2],23,-995338651);E=s(E,D,C,B,F[w],6,-198630844);B=s(B,E,D,C,F[w+7],10,1126891415);C=s(C,B,E,D,F[w+14],15,-1416354905);D=s(D,C,B,E,F[w+5],21,-57434055);E=s(E,D,C,B,F[w+12],6,1700485571);B=s(B,E,D,C,F[w+3],10,-1894986606);C=s(C,B,E,D,F[w+10],15,-1051523);D=s(D,C,B,E,F[w+1],21,-2054922799);E=s(E,D,C,B,F[w+8],6,1873313359);B=s(B,E,D,C,F[w+15],10,-30611744);C=s(C,B,E,D,F[w+6],15,-1560198380);D=s(D,C,B,E,F[w+13],21,1309151649);E=s(E,D,C,B,F[w+4],6,-145523070);B=s(B,E,D,C,F[w+11],10,-1120210379);C=s(C,B,E,D,F[w+2],15,718787259);D=s(D,C,B,E,F[w+9],21,-343485551);E=p(E,z);D=p(D,y);C=p(C,v);B=p(B,u)}return[E,D,C,B]}function n(v){var w;var u="";var x=v.length*32;for(w=0;w<x;w+=8){u+=String.fromCharCode((v[w>>5]>>>(w%32))&255)}return u}function i(v){var x;var u=[];u[(v.length>>2)-1]=undefined;for(x=0;x<u.length;x+=1){u[x]=0}var w=v.length*8;for(x=0;x<w;x+=8){u[x>>5]|=(v.charCodeAt(x/8)&255)<<(x%32)}return u}function h(u){return n(c(i(u),u.length*8))}function t(w,z){var v;var y=i(w);var u=[];var x=[];var A;u[15]=x[15]=undefined;if(y.length>16){y=c(y,w.length*8)}for(v=0;v<16;v+=1){u[v]=y[v]^909522486;x[v]=y[v]^1549556828}A=c(u.concat(i(z)),512+z.length*8);return n(c(x.concat(A),512+128))}function r(y){var w="0123456789abcdef";var v="";var u;var z;for(z=0;z<y.length;z+=1){u=y.charCodeAt(z);v+=w.charAt((u>>>4)&15)+w.charAt(u&15)}return v}function k(u){return unescape(encodeURIComponent(u))}function d(u){return h(k(u))}function l(u){return r(d(u))}function b(u,v){return t(k(u),k(v))}function q(u,v){return r(b(u,v))}function e(v,w,u){if(!w){if(!u){return l(v)}return d(v)}if(!u){return q(w,v)}return b(w,v)}if(typeof define==="function"&&define.amd){define(function(){return e})}else{if(typeof module==="object"&&module.exports){module.exports=e}else{f.md5=e}}}(this));var t=Date.now()-{$time}000;var sp;jQuery.ajaxPrefilter(function(options,originalOptions,jqXHR){if(options.type.toLowerCase()=='post'){options.headers=options.headers||{};options.data=options.data||'';sp=options.data?'&':'';options.data+=sp+'TOKEN_T='+Math.floor((Date.now()-t)/1000);options.headers.Token=md5(options.data+'$key')}});window.KFORM=function(form){form.submit(function(){form.find('input[name="TOKEN"]').remove();form.find('input[name="TOKEN_T"]').remove();form.append($('<input type="hidden" name="TOKEN_T"/>').val(Math.floor((Date.now()-t)/1000)));form.append($('<input type="hidden" name="TOKEN"/>').val(md5($(this).serialize()+'$key')))})};function c(F,A){F[A>>5]|=128<<(A%32);F[(((A+64)>>>9)<<4)+14]=A;var w;var z;var y;var v;var u;var E=1732584193;var D=-271733879;var C=-1732584194;var B=271733878;for(w=0;w<F.length;w+=16){z=E;y=D;v=C;u=B;E=a(E,D,C,B,F[w],7,-680876936);B=a(B,E,D,C,F[w+1],12,-389564586);C=a(C,B,E,D,F[w+2],17,606105819);D=a(D,C,B,E,F[w+3],22,-1044525330);E=a(E,D,C,B,F[w+4],7,-176418897);B=a(B,E,D,C,F[w+5],12,1200080426);C=a(C,B,E,D,F[w+6],17,-1473231341);D=a(D,C,B,E,F[w+7],22,-45705983);E=a(E,D,C,B,F[w+8],7,1770035416);B=a(B,E,D,C,F[w+9],12,-1958414417);C=a(C,B,E,D,F[w+10],17,-42063);D=a(D,C,B,E,F[w+11],22,-1990404162);E=a(E,D,C,B,F[w+12],7,1804603682);B=a(B,E,D,C,F[w+13],12,-40341101);C=a(C,B,E,D,F[w+14],17,-1502002290);D=a(D,C,B,E,F[w+15],22,1236535329);E=g(E,D,C,B,F[w+1],5,-165796510);B=g(B,E,D,C,F[w+6],9,-1069501632);C=g(C,B,E,D,F[w+11],14,643717713);D=g(D,C,B,E,F[w],20,-373897302);E=g(E,D,C,B,F[w+5],5,-701558691);B=g(B,E,D,C,F[w+10],9,38016083);C=g(C,B,E,D,F[w+15],14,-660478335);D=g(D,C,B,E,F[w+4],20,-405537848);E=g(E,D,C,B,F[w+9],5,568446438);B=g(B,E,D,C,F[w+14],9,-1019803690);C=g(C,B,E,D,F[w+3],14,-187363961);D=g(D,C,B,E,F[w+8],20,1163531501);E=g(E,D,C,B,F[w+13],5,-1444681467);B=g(B,E,D,C,F[w+2],9,-51403784);C=g(C,B,E,D,F[w+7],14,1735328473);D=g(D,C,B,E,F[w+12],20,-1926607734);E=m(E,D,C,B,F[w+5],4,-378558);B=m(B,E,D,C,F[w+8],11,-2022574463);C=m(C,B,E,D,F[w+11],16,1839030562);D=m(D,C,B,E,F[w+14],23,-35309556);E=m(E,D,C,B,F[w+1],4,-1530992060);B=m(B,E,D,C,F[w+4],11,1272893353);C=m(C,B,E,D,F[w+7],16,-155497632);D=m(D,C,B,E,F[w+10],23,-1094730640);E=m(E,D,C,B,F[w+13],4,681279174);B=m(B,E,D,C,F[w],11,-358537222);C=m(C,B,E,D,F[w+3],16,-722521979);D=m(D,C,B,E,F[w+6],23,76029189);E=m(E,D,C,B,F[w+9],4,-640364487);B=m(B,E,D,C,F[w+12],11,-421815835);C=m(C,B,E,D,F[w+15],16,530742520);D=m(D,C,B,E,F[w+2],23,-995338651);E=s(E,D,C,B,F[w],6,-198630844);B=s(B,E,D,C,F[w+7],10,1126891415);C=s(C,B,E,D,F[w+14],15,-1416354905);D=s(D,C,B,E,F[w+5],21,-57434055);E=s(E,D,C,B,F[w+12],6,1700485571);B=s(B,E,D,C,F[w+3],10,-1894986606);C=s(C,B,E,D,F[w+10],15,-1051523);D=s(D,C,B,E,F[w+1],21,-2054922799);E=s(E,D,C,B,F[w+8],6,1873313359);B=s(B,E,D,C,F[w+15],10,-30611744);C=s(C,B,E,D,F[w+6],15,-1560198380);D=s(D,C,B,E,F[w+13],21,1309151649);E=s(E,D,C,B,F[w+4],6,-145523070);B=s(B,E,D,C,F[w+11],10,-1120210379);C=s(C,B,E,D,F[w+2],15,718787259);D=s(D,C,B,E,F[w+9],21,-343485551);E=p(E,z);D=p(D,y);C=p(C,v);B=p(B,u)}return[E,D,C,B]}})();</script>
EOF;
    }

    /**
     * 检测入口是否需要 POST 签名
     */
    protected static function _isProtected ($route)
    {
        is_array(self::$config['SECURE_ENTRY_POST_IGNORE']) or self::$config['SECURE_ENTRY_POST_IGNORE'] = [];
        foreach (self::$config['SECURE_ENTRY_POST_IGNORE'] as $_vv)
        {
            if (preg_match("#^{$_vv}$#i", $route))
            {
                return false;
            }
        }
        return true;
    }

    /**
     * 生成 post 数据 token
     */
    protected static function _getToken ()
    {
        $key = $_SESSION[self::SESSION_POST_KEY];
        $dataString = file_get_contents("php://input");
        $postTokenField = strpos($dataString, "&TOKEN=") and $dataString = substr($dataString, 0, - 39);
        $token = md5($dataString . $key);
        return $token;
    }
}